package com.youku.mson.parser;

import static com.youku.mson.lexer.TokenType.COLON;
import static com.youku.mson.lexer.TokenType.COMMA;
import static com.youku.mson.lexer.TokenType.EOF;
import static com.youku.mson.lexer.TokenType.LBRACE;
import static com.youku.mson.lexer.TokenType.LBRACKET;
import static com.youku.mson.lexer.TokenType.RBRACE;
import static com.youku.mson.lexer.TokenType.RBRACKET;
import static com.youku.mson.lexer.TokenType.STRING;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.youku.mson.lexer.Lexer;
import com.youku.mson.lexer.Token;
import com.youku.mson.lexer.TokenType;
import com.youku.mson.type.ArrayJsonValue;
import com.youku.mson.type.BooleanJsonValue;
import com.youku.mson.type.JsonValue;
import com.youku.mson.type.NullJsonValue;
import com.youku.mson.type.NumberJsonValue;
import com.youku.mson.type.ObjectJsonValue;
import com.youku.mson.type.PairJsonValue;
import com.youku.mson.type.StringJsonValue;

public class JsonParser {

	private List<Token> tokens;

	private int pos = 0;

	public JsonParser(List<Token> tokens) {
		super();
		this.tokens = tokens;
	}

	public JsonParser(Lexer lexer) {
		this.tokens = lexer.tokens();
	}

	/**
	 * array|object
	 * 
	 * @return
	 */
	public JsonValue parse() {
		Token token = peekToken();
		switch (token.getTokenType()) {
		case LBRACE:
			return parseObject();
		case LBRACKET:
			return parseArray();
		default:
			throw new RuntimeException("非法字符" + token);
		}
	}

	/**
	 * [] | [elements]
	 * 
	 * @return
	 */
	private JsonValue parseArray() {
		consumeToken(LBRACKET);
		JsonValue array = parseElements();
		consumeToken(RBRACKET);
		return array;
	}

	/**
	 * value(,elements)
	 * 
	 * @return
	 */
	private JsonValue parseElements() {
		List<JsonValue> elements = new ArrayList<>();

		elements.add(parseValue());
		Token token = peekToken();
		while (token.getTokenType() == COMMA) {
			nextToken();
			elements.add(parseValue());
			token = peekToken();
		}

		return new ArrayJsonValue(elements);
	}

	/**
	 * {} | {members}
	 * 
	 * @return
	 */
	private JsonValue parseObject() {
		consumeToken(LBRACE);
		Token token = peekToken();

		JsonValue jsonValue = null;
		if (token.getTokenType() == RBRACE) {
			List<JsonValue> members = Collections.emptyList();
			jsonValue = new ObjectJsonValue(members);
		} else {
			jsonValue = parseMembers();
		}
		consumeToken(RBRACE);
		return jsonValue;
	}

	/**
	 * pair(,members)
	 * 
	 * @return
	 */
	private JsonValue parseMembers() {
		List<JsonValue> members = new ArrayList<>();

		members.add(parsePair());

		Token current = peekToken();
		while (current.getTokenType() == COMMA) {
			nextToken();
			members.add(parsePair());
			current = peekToken();
		}
		return new ObjectJsonValue(members);
	}

	/**
	 * string:value
	 * 
	 * @return
	 */
	private JsonValue parsePair() {
		Token key = peekToken();
		if (key.getTokenType() == STRING) {
			nextToken();
			consumeToken(COLON);
			JsonValue value = parseValue();
			return new PairJsonValue(key.getValue(), value);
		} else {
			throw new RuntimeException("缺少" + key.getTokenType().getName());
		}
	}

	/**
	 * null|true|false|int|float|string|object|array
	 * 
	 * @return
	 */
	private JsonValue parseValue() {
		Token value = peekToken();
		switch (value.getTokenType()) {
		case NULL:
			nextToken();
			return new NullJsonValue();
		case TRUE:
			nextToken();
			return new BooleanJsonValue(true);
		case FALSE:
			nextToken();
			return new BooleanJsonValue(false);
		case NUMBER:
			nextToken();
			return new NumberJsonValue(value.getValue());
		case STRING:
			nextToken();
			return new StringJsonValue(value.getValue());
		case LBRACE:
			return parseObject();
		case LBRACKET:
			return parseArray();
		default:
			throw new RuntimeException("无法识别" + value.getTokenType().getName());
		}
	}

	private void consumeToken(TokenType type) {
		if (nextToken().getTokenType() != type) {
			throw new RuntimeException("缺少" + type.getName());
		}
	}

	private Token peekToken() {
		if (pos >= tokens.size()) {
			return new Token("EOF", EOF);
		} else {
			return tokens.get(pos);
		}
	}

	private Token nextToken() {
		if (pos >= tokens.size()) {
			return new Token("EOF", EOF);
		} else {
			return tokens.get(pos++);
		}
	}

}
